﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using ApprovalFlow.Interface;
using ApprovalFlow.Primitives;

namespace ApprovalFlow.Models
{
    internal class WorkflowHost : IWorkflowHost
    {
        private readonly IServiceProvider _serviceProvider;
        private readonly IWorkflowRepository _workflowRepository;
        private readonly ISubscriptionRepository _subscriptionRepository;
        private readonly IDateTimeProvider _dateTimeProvider;
        private readonly IWorkflowRegistry _workflowRegistry;
        private readonly IExecutionPointerFactory _pointerFactory;
        private readonly IEnumerable<IBackgroundTask> _backgroundTasks;
        private readonly IEventRepository _eventRepository;
        private readonly ILogger _logger;
        private readonly IQueueProvider _queueProvider;
        private readonly IWorkflowExecutor _workflowExecutor;
        private readonly IUserProvider _userProvider;


        public WorkflowHost(
            IServiceProvider serviceProvider, IWorkflowRepository workflowRepository, IDateTimeProvider dateTimeProvider,
            IWorkflowRegistry workflowRegistry, IExecutionPointerFactory pointerFactory, IEventRepository eventRepository,
            IEnumerable<IBackgroundTask> backgroundTasks, IQueueProvider queueProvider, ILoggerFactory loggerFactory,
            IWorkflowExecutor workflowExecutor, IUserProvider userProvider, ISubscriptionRepository subscriptionRepository
            )
        {
            _serviceProvider = serviceProvider;
            _workflowRepository = workflowRepository;
            _dateTimeProvider = dateTimeProvider;
            _workflowRegistry = workflowRegistry;
            _pointerFactory = pointerFactory;
            _eventRepository = eventRepository;
            _backgroundTasks = backgroundTasks;
            _queueProvider = queueProvider;
            _workflowExecutor = workflowExecutor;
            _userProvider = userProvider;
            _subscriptionRepository = subscriptionRepository;
            _logger = loggerFactory.CreateLogger<WorkflowHost>();
        }

        public async Task PublishEvent(string eventKey, object eventData, DateTime? effectiveDate = null)
        {
            _logger.LogDebug("Creating event {0} {1}", eventKey);
            var sub = await _subscriptionRepository.GetSubscription(eventKey);
            Event evt = new()
            {
                EventData = eventData,
                EventKey = eventKey,
                EventName = sub?.EventName,
                EventTime = _dateTimeProvider.Now,
                IsProcessed = false,
            };

            if (effectiveDate.HasValue)
                evt.EventTime = effectiveDate.Value.ToUniversalTime();
            else
                evt.EventTime = _dateTimeProvider.UtcNow;
            string event_id = await _eventRepository.CreateEvent(evt);

            if (sub is not null)
            {
                try
                {
                    var workflow = await _workflowRepository.GetWorkflowInstance(sub.InstanceId);
                    var pointers = workflow.ExecutionPointers.Where(p => p.Id == sub.ExecutionPointerId && !p.EventPublished && p.EndTime == null);
                    foreach (var p in pointers)
                    {
                        p.EventData = evt.EventData;
                        p.EventPublished = true;
                        p.Active = true;
                    }

                    await _workflowExecutor.Execute(workflow);
                    await _eventRepository.MarkEventProcessed(event_id);
                }
                catch (Exception)
                {
                    await _eventRepository.MarkEventUnprocessed(event_id);
                    throw;
                }
            }
            else
            {
                await _eventRepository.MarkEventProcessed(event_id);
            }
        }

        public void RegisterWorkflow<TWorkflow>() where TWorkflow : IWorkflow
        {
            throw new NotImplementedException();
        }

        public void RegisterWorkflow<TWorkflow, TData>()
            where TWorkflow : IWorkflow<TData>
            where TData : new()
        {
            throw new NotImplementedException();
        }

        public void Start()
        {
            _queueProvider.Start();

            foreach (var task in _backgroundTasks)
            {
                task.Start();
            }
        }

        public void Stop()
        {
            _queueProvider.Stop();

            foreach (var task in _backgroundTasks)
            {
                task.Stop();
            }
        }

        public async Task<string> StartWorkflow(string process_code, object data)
        {
            return await StartWorkflow(process_code, data, null);
        }

        public async Task<string> StartWorkflow(string process_code, object data, string reference)
        {
            return await StartWorkflow(process_code, data, reference, default, default);
        }

        public async Task<string> StartWorkflow(string process_code, object data, string reference, Dictionary<string, string[]> optional_list = default, string[] cc_list = default, string cc_position = "START")
        {
            var def = this._workflowRegistry.GetDefinition(process_code);
            var workflow = new WorkflowInstance
            {
                ProcessCode = process_code,
                Version = def.Version,
                Data = data,
                Reference = reference,
                Description = def.Description,
                CreateTime = _dateTimeProvider.Now,
                CCList = cc_list,
                CCPosition = cc_position,
                CustomUserList = optional_list,
                UserID = _userProvider.UserID,
                DeptID = _userProvider.DeptID,
                Status = WorkflowStatus.Runnable,
            };
            var pointers = _pointerFactory.BuildGenesisPointer(def, workflow);
            workflow.ExecutionPointers.AddRange(pointers);
            workflow.NextExecution = workflow.ExecutionPointers.First(x => string.IsNullOrEmpty(x.PredecessorId)).Id;

            var id = await this._workflowRepository.CreateNewWorkflow(workflow);
            await _workflowExecutor.Execute(workflow);
            return id;
        }

        public async Task TerminateWorkflow(string workflowId)
        {
            await _workflowRepository.TerminateWorkflow(workflowId);
        }
    }
}